Skip to content

feat(fm-backend): extend legacy-metadata self-repair to zellij and cmux backends - #39

Merged
trillium merged 8 commits into
mainfrom
fm/teardown-stale-fix
Aug 4, 2026
Merged

feat(fm-backend): extend legacy-metadata self-repair to zellij and cmux backends#39
trillium merged 8 commits into
mainfrom
fm/teardown-stale-fix

Conversation

@trillium

@trillium trillium commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Intent

Extend the herdr legacy-metadata self-repair pattern (PR #20, commit ba2a480) in bin/fm-backend.sh's fm_backend_validate_task_endpoint to the zellij and cmux backends: when endpoint_task_id is absent from legacy meta, re-derive live identity (pane/tab ownership and label match for zellij; surface/workspace membership and title match for cmux) and self-repair the binding only when it provably still matches the task, otherwise refuse. orca is intentionally left refusing unconditionally since it has no verified live-identity-check primitive. Adds fm_backend_zellij_pane_verifies_task, fm_backend_cmux_workspace_matches_label, and fm_backend_cmux_surface_verifies_task, plus matching tests in tests/fm-teardown-endpoint-safety.test.sh.

What Changed

  • Extended fm_backend_validate_task_endpoint to support legacy-metadata self-repair in zellij and cmux backends, detecting missing endpoint task IDs and re-deriving live identity to repair bindings only when they provably still match the original task
  • Added comprehensive test suite validating self-repair behavior and correctly rejecting recycled resources
  • Updated backend documentation, enhanced interactive OAuth authentication in claude-account.sh, and exempted CronCreate/CronDelete from fm-subagent guard for durable schedule management

Risk Assessment

✅ Low: The implementation correctly extends the herdr self-repair pattern to zellij and cmux backends with proper defensive checks, clear error messages, and comprehensive test coverage including both success and failure scenarios.

Testing

Ran the full test suite for endpoint safety validation. All 11 tests pass, including three critical new tests: (1) zellij legacy metadata self-repair when live pane/tab still matches the task, and refusal without mutation when tab is recycled; (2) cmux legacy metadata self-repair when live workspace/surface still matches the task, and refusal without mutation when surface is recycled; (3) orca validation continues to refuse unconditionally with proper documentation. The three new functions (fm_backend_zellij_pane_verifies_task, fm_backend_cmux_workspace_matches_label, fm_backend_cmux_surface_verifies_task) are correctly implemented and invoked in the validation flow.

Evidence: Full test output
ok - fm-teardown: missing, empty, malformed, ambiguous, and task-mismatched endpoints refuse before every mutation or runtime call
ok - cleanup identity: valid tmux, Herdr, Zellij, Orca, and cmux records validate while every empty backend target refuses
herdr endpoint self-repair: appending endpoint_task_id=herdr-legacy-task to metadata for legacy task herdr-legacy-task
REFUSED: legacy Herdr endpoint metadata for task herdr-recycled-task lacks an exact task binding; pane verification failed or pane does not belong to this task; preserving task state.
ok - fm_backend_validate_task_endpoint (herdr): legacy metadata self-repairs when the live pane still matches the task, and refuses without mutation when the pane now belongs to a different (recycled) task
zellij endpoint self-repair: appending endpoint_task_id=zellij-legacy-task to metadata for legacy task zellij-legacy-task
REFUSED: legacy Zellij endpoint metadata for task zellij-recycled-task lacks an exact task binding; pane verification failed or pane does not belong to this task; preserving task state.
ok - fm_backend_validate_task_endpoint (zellij): legacy metadata self-repairs when the live pane/tab still matches the task, and refuses without mutation when the tab now belongs to a different (recycled) task
cmux endpoint self-repair: appending endpoint_task_id=cmux-legacy-task to metadata for legacy task cmux-legacy-task
REFUSED: legacy cmux endpoint metadata for task cmux-recycled-task lacks an exact task binding; surface verification failed or surface does not belong to this task; preserving task state.
ok - fm_backend_validate_task_endpoint (cmux): legacy metadata self-repairs when the live workspace/surface still matches the task, and refuses without mutation when the workspace now belongs to a different (recycled) task
ok - tmux backend: direct empty target returns nonzero without invoking tmux
ok - process cleanup: creation-time PID identity removes only the exact child and preserves the control child
ok - fm-teardown: exact tmux cleanup preserves invalid and prefix-matched neighbors while removing only the recorded target

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⏭️ **Rebase** - skipped

Step was skipped.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • ⚠️ tests/fm-teardown-endpoint-safety.test.sh:234 - Test error message says 'should validate' when the test expects validation to fail. The message is confusing because the test checks that rc != 0 (expects refusal), but the error message text suggests success should occur.
  • ⚠️ tests/fm-teardown-endpoint-safety.test.sh:285 - Same as line 234: test error message says 'should validate' when the expected behavior is validation failure. This will confuse developers reading the test or debugging failures.

🔧 Fix: Fix test error messages: recycled resources should refuse, not validate
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-teardown-endpoint-safety.test.sh
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Summary by CodeRabbit

  • New Features

    • Improved validation of terminal workspaces, panes, surfaces, and task associations.
    • Added safer recovery for legacy terminal session metadata.
    • Added Keychain-based authentication for multi-account Claude Code setup.
    • Enabled approved durable schedule-management tools while blocking unknown schedule tools.
  • Bug Fixes

    • Prevented cleanup or reuse of terminal endpoints belonging to another task.
    • Rejected missing or malformed authentication tokens instead of falling back to interactive login.
  • Documentation

    • Updated terminal backend and multi-account setup guidance.

…ve OAuth auth

Current Claude Code (2.1.x) reads its global config from
$CLAUDE_CONFIG_DIR/.claude.json (join(CLAUDE_CONFIG_DIR ?? homedir,
'.claude.json')) when CLAUDE_CONFIG_DIR is set, not from a .claude.json in
the parent dir. The launcher pre-seeded the parent, which CC ignores, so a
fresh account still entered the first-run onboarding flow. The onboarding
gate is a single key, hasCompletedOnboarding===true. Write the pre-seed
(hasCompletedOnboarding + numStartups + per-project hasTrustDialogAccepted)
into $CLAUDE_CONFIG_DIR/.claude.json instead.

Interactive sessions authenticate from the per-account OAuth credential file
$CLAUDE_CONFIG_DIR/.credentials.json (the claudeAiOauth blob from
'claude /login'), not a setup-token / CLAUDE_CODE_OAUTH_TOKEN (env-supplied,
non-refreshable, print/CI-only). Clarify the launcher's credential
requirement and error path accordingly and document both mechanics.

Tests updated to assert the pre-seed lands at the config-dir path CC reads
(and not the parent), the onboarding key set, and that the auth refusal
steers to OAuth /login rather than a setup-token.
….json) + interactive OAuth auth (robots-4evu)
…m A)

A cron ROUTINE is durable, persistent, and inspectable via CronList, so
creating/deleting one is not the ephemeral, session-bound, fleet-invisible
delegation the guard targets; /schedule relies on CronCreate. Add a third
exempt category SCHEDULE_MGMT_TOOLS (croncreate crondelete), wired into the
existing allow loop, deliberately narrower than the cron/schedul stems so
unknown future cron/schedule tools stay caught. DELEGATION_STEMS unchanged.

Tests lock the exemption at exactly two names: CronCreate/CronDelete/CronList
allow; ScheduleCreate and the full delegation hazard set (Task, Agent,
Workflow, spawn, dispatch, subagent, Worktree, SendMessage, Handoff, Remote)
stay denied.
…ble schedule mgmt), test-locked (robots-sy7m)
…eychain (robots-4evu)

Adopt the setup-token model (Trillium's direction, 'we use setup token now'):
each account authenticates with a ~1yr CLAUDE_CODE_OAUTH_TOKEN (sk-ant-oat01-)
from keychain service ccjuggler-acc<N>, honored by current Claude Code
interactive sessions — no mid-fleet-run logout, one source of truth with
ccjuggler/juggle. Supersedes the interim per-account OAuth .credentials.json
approach. Retains the onboarding-skip fix (pre-seed CLAUDE_CONFIG_DIR/.claude.json,
where current CC reads it). Tests reconciled: hermetic 'security' keychain mock;
assert missing/malformed token refuse loudly and a valid token exports as
CLAUDE_CODE_OAUTH_TOKEN.
Mirrors the existing herdr self-repair pattern (PR #20): when
endpoint_task_id is absent from legacy meta, re-derive identity from
the live backend and self-repair the binding only when it provably
still matches, otherwise refuse.

- bin/backends/zellij.sh: add fm_backend_zellij_pane_verifies_task,
  checking the pane's current owning tab against the recorded tab_id
  and the tab's label via the existing scoped/bare ambiguity-safe
  fm_backend_zellij_tab_matches_label.
- bin/backends/cmux.sh: add fm_backend_cmux_workspace_matches_label
  (exact-id lookup, never falling back to a label-only search) and
  fm_backend_cmux_surface_verifies_task composing it with the existing
  fm_backend_cmux_surface_exists.
- bin/fm-backend.sh: wire both into
  fm_backend_validate_task_endpoint's zellij and cmux blocks, matching
  herdr's self-repair-or-refuse structure. orca is left unchanged
  (refusing unconditionally), documented inline: no verified
  live-identity-check primitive exists to prove a recorded Orca
  terminal/worktree still belongs to a task.
- tests/fm-teardown-endpoint-safety.test.sh: add
  test_zellij_legacy_meta_self_repair_and_recycled_pane_refusal and
  test_cmux_legacy_meta_self_repair_and_recycled_pane_refusal,
  mirroring the existing herdr test's self-repair and
  recycled-pane-refuses-without-mutation assertions.

Squash-merge landed-work detection and herdr self-repair were already
implemented and tested on main (commit ba2a480, PR #20); this change
covers the remaining zellij/cmux gap.

Full fm-teardown.test.sh suite: 49/49 pass, no regressions.
bin/fm-lint.sh: clean.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds live identity checks for legacy Zellij and cmux endpoints, replaces Claude account login seeding with validated Keychain setup tokens, and allowlists exact durable schedule-management tools. Tests and documentation cover the new behavior.

Changes

Endpoint identity verification

Layer / File(s) Summary
Live endpoint identity primitives
bin/backends/cmux.sh, bin/backends/zellij.sh
The adapters validate workspace, surface, pane, tab, and task identities using scoped or unambiguous labels.
Legacy metadata repair
bin/fm-backend.sh, docs/configuration.md, docs/herdr-backend.md
The dispatcher repairs legacy Zellij and cmux metadata after successful verification. Orca continues to refuse unbound metadata.
Endpoint repair regression coverage
tests/fm-teardown-endpoint-safety.test.sh, docs/cmux-backend.md, docs/zellij-backend.md
Tests and backend documentation cover matching endpoint repair and refusal for recycled or ambiguous endpoints.

Claude setup-token authentication

Layer / File(s) Summary
Keychain authentication and account state
bin/claude-account.sh
The launcher validates per-account Keychain setup tokens, exports CLAUDE_CODE_OAUTH_TOKEN, and initializes account-local Claude state.
Authentication documentation and tests
docs/configuration.md, tests/claude-account.test.sh
Documentation and tests cover token provisioning, validation failures, token export, MCP approval, and .claude.json initialization.

Schedule-tool allowlist

Layer / File(s) Summary
Schedule-tool classification and coverage
bin/fm-subagent-pretool-check.sh, tests/fm-subagent-pretool-check.test.sh, docs/subagent-guard.md
Exact CronCreate and CronDelete tools are allowed. Near matches and delegation hazards remain denied.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant fm_backend
  participant Backend_adapter
  participant Live_endpoint
  participant Endpoint_metadata
  fm_backend->>Backend_adapter: verify legacy endpoint
  Backend_adapter->>Live_endpoint: query current ownership and label
  Live_endpoint-->>Backend_adapter: return live identity
  Backend_adapter-->>fm_backend: return verification result
  fm_backend->>Endpoint_metadata: repair binding when verified
Loading
sequenceDiagram
  participant claude_account
  participant macOS_Keychain
  participant Claude_Code
  claude_account->>macOS_Keychain: read account setup token
  macOS_Keychain-->>claude_account: return validated token
  claude_account->>Claude_Code: export CLAUDE_CODE_OAUTH_TOKEN
  claude_account->>Claude_Code: launch with account-local configuration
Loading

Possibly related PRs

  • trillium/firstmate#38: Both changes modify Claude account authentication and per-account configuration behavior.

Suggested reviewers: kunchenguid

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes extending legacy-metadata self-repair to the Zellij and cmux backends.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fm/teardown-stale-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
bin/fm-subagent-pretool-check.sh (1)

81-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move reusable policy rationale out of the shell comments.

The changed comments duplicate task-specific policy knowledge in the implementation and tests. Keep only the local invariant that the exemption contains exact names and that near matches remain denied. Move the robots-sy7m rationale, /schedule dependency, and delegation-policy explanation to committed AGENTS.md or authoritative documentation.

  • bin/fm-subagent-pretool-check.sh#L81-L96: shorten the rationale around SCHEDULE_MGMT_TOOLS.
  • tests/fm-subagent-pretool-check.test.sh#L44-L62: keep the fixtures and a concise contract comment.
  • tests/fm-subagent-pretool-check.test.sh#L103-L105: keep the exclusion comment concise.
  • tests/fm-subagent-pretool-check.test.sh#L159-L192: keep the assertions and move reusable rationale out of the tests.

As per coding guidelines: Keep reusable project knowledge in the project's committed AGENTS.md, fleet-local facts in data/learnings.md, captain preferences in the appropriate captain file, and task findings with the task or scout report.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/fm-subagent-pretool-check.sh` around lines 81 - 96, Shorten the comments
around SCHEDULE_MGMT_TOOLS in bin/fm-subagent-pretool-check.sh#81-96 to state
only the exact-name exemption and near-match denial invariant; move the
robots-sy7m rationale, /schedule dependency, and delegation-policy explanation
to committed AGENTS.md or authoritative documentation. In
tests/fm-subagent-pretool-check.test.sh#44-62, `#103-105`, and `#159-192`, retain
the fixtures, assertions, and concise contract comments while removing
duplicated reusable policy rationale.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/claude-account.sh`:
- Around line 131-139: Remove the unconditional enableAllProjectMcpServers
assignment from the account bootstrap in bin/claude-account.sh, preserving
Claude Code’s default per-server approval unless an explicit captain opt-in
targets a known project/server. Update docs/configuration.md and the account
bootstrap tests to document and verify the replacement behavior.

In `@bin/fm-backend.sh`:
- Around line 500-502: Preserve metadata record boundaries when repairing
bindings: in bin/fm-backend.sh lines 500-502, update the Zellij repair path
around fm_backend_zellij_pane_verifies_task to append endpoint_task_id through a
format-aware metadata writer that inserts a missing final LF; apply the same
writer to the cmux repair path at lines 554-556. In
tests/fm-teardown-endpoint-safety.test.sh lines 215-236 and 264-287, add
no-final-LF Zellij and cmux fixtures and assert each repaired binding is
standalone without corrupting the preceding field.

In `@docs/configuration.md`:
- Around line 282-284: Update the fenced code block surrounding the security
command to declare the sh language, changing the opening fence from unlabeled to
sh while preserving the command and closing fence.

In `@tests/claude-account.test.sh`:
- Around line 162-165: Update the fixture assertion in the account configuration
test to search for the actual shared marker written by the fixture, “shared” or
“leak,” instead of “acct1-secret.” Keep the symlink assertion unchanged and
ensure copying the shared .claude.json into the account configuration causes the
test to fail.

---

Nitpick comments:
In `@bin/fm-subagent-pretool-check.sh`:
- Around line 81-96: Shorten the comments around SCHEDULE_MGMT_TOOLS in
bin/fm-subagent-pretool-check.sh#81-96 to state only the exact-name exemption
and near-match denial invariant; move the robots-sy7m rationale, /schedule
dependency, and delegation-policy explanation to committed AGENTS.md or
authoritative documentation. In tests/fm-subagent-pretool-check.test.sh#44-62,
`#103-105`, and `#159-192`, retain the fixtures, assertions, and concise contract
comments while removing duplicated reusable policy rationale.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29ccd467-885c-4178-b28b-7d875dd4b027

📥 Commits

Reviewing files that changed from the base of the PR and between e46a16e and 6091c3e.

📒 Files selected for processing (9)
  • bin/backends/cmux.sh
  • bin/backends/zellij.sh
  • bin/claude-account.sh
  • bin/fm-backend.sh
  • bin/fm-subagent-pretool-check.sh
  • docs/configuration.md
  • tests/claude-account.test.sh
  • tests/fm-subagent-pretool-check.test.sh
  • tests/fm-teardown-endpoint-safety.test.sh

Comment thread bin/claude-account.sh
Comment on lines +131 to +139
# Auto-approve project-scoped (.mcp.json) MCP servers so a fresh account home
# is not dropped into the per-server "New MCP server found" prompt on first
# encounter. .claude.json is per-account (never symlinked, to avoid OAuth/
# project-state leak), so an account never inherits the primary's MCP approvals
# - this global flag is the durable equivalent of choosing "use this and all
# future MCP servers in this project".
if not data.get("enableAllProjectMcpServers"):
data["enableAllProjectMcpServers"] = True
changed = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)(bin/claude-account\.sh|tests/fm-spawn-account\.test\.sh|docs/configuration\.md|$\.claude\.json|\.mcp\.json)$' || true

echo "== outline =="
ast-grep outline bin/claude-account.sh || true

echo "== relevant source lines =="
cat -n bin/claude-account.sh | sed -n '1,220p'

echo "== docs / config mentions =="
if [ -f docs/configuration.md ]; then rg -n "enableAllProjectMcpServers|enable.*Mcp|project.*mcp|\.mcp\.json" docs/configuration.md || true; fi

echo "== tests/mentions =="
rg -n "enableAllProjectMcpServers|enable.*Mcp|project.*mcp|\.mcp\.json|claude-account|CLAUDE_TRUST_DIR" bin tests docs -S || true

echo "== repo root config files =="
find . -maxdepth 3 \( -name '.claude.json' -o -name '.mcp.json' -o -name 'claude.json' -o -name 'mcp.json' \) -print 2>/dev/null || true

Repository: trillium/firstmate

Length of output: 16134


🌐 Web query:

Claude Code MCP project .mcp.json command enableAllProjectMcpServers documentation

💡 Result:

In Claude Code, enableAllProjectMcpServers is a configuration setting used to automatically approve and enable all MCP servers defined in a project's.mcp.json file, bypassing the individual per-server prompt [1][2][3]. Key points regarding its usage and limitations: 1. Purpose and Usage: By setting "enableAllProjectMcpServers": true in your Claude Code settings (typically in ~/.claude/settings.json), you authorize all project-scoped servers to run without needing to manually approve them each time [2][3]. 2. Important Security Constraint: As of Claude Code v2.1.196, this setting is ignored if the workspace folder is considered "untrusted" [4][1]. Even if "enableAllProjectMcpServers": true is committed to a project's.claude/settings.json file, Claude Code will not honor it until you have explicitly trusted the workspace by running the claude command in that folder and accepting the trust dialog [4][1]. This prevents a potential security risk where a cloned, untrusted repository could automatically execute its own configured servers [4][1]. 3. Recommended Alternative: For better security control, it is often recommended to use the enabledMcpjsonServers setting instead [1]. This allows you to explicitly list only the specific servers you trust by name, rather than using a blanket true setting that enables every server defined in the.mcp.json file [1]. 4. Troubleshooting: If your.mcp.json servers are stuck at "Pending approval," it is frequently because the workspace trust gate has not been cleared [4][1]. Additionally, note that entries in disabledMcpjsonServers will override enableAllProjectMcpServers, meaning a server explicitly disabled will not run regardless of your global "enable all" setting [1].

Citations:


Security Misconfiguration (CWE-829): Inclusion of Functionality from Untrusted Control Sphere

Reachability: Internal

Do not persist enableAllProjectMcpServers=true on account bootstrap.

bin/claude-account.sh writes this global approval into $CLAUDE_CONFIG_DIR/.claude.json, and docs/configuration.md documents it as intentional. Add MCP approval requirements behind an explicit captain opt-in for a known project/server, or keep Claude Code’s default per-server approval. Update docs/configuration.md and the account bootstrap tests with the replacement behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/claude-account.sh` around lines 131 - 139, Remove the unconditional
enableAllProjectMcpServers assignment from the account bootstrap in
bin/claude-account.sh, preserving Claude Code’s default per-server approval
unless an explicit captain opt-in targets a known project/server. Update
docs/configuration.md and the account bootstrap tests to document and verify the
replacement behavior.

Comment thread bin/fm-backend.sh
Comment on lines +500 to +502
if fm_backend_zellij_pane_verifies_task "$recorded_session" "$tab" "$pane" "$id" 2>/dev/null; then
echo "zellij endpoint self-repair: appending endpoint_task_id=$id to metadata for legacy task $id" >&2
printf 'endpoint_task_id=%s\n' "$id" >> "$meta" || return 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve metadata record boundaries during repair.

At Line 502 and Line 556, a metadata file accepted without a final LF causes endpoint_task_id to concatenate with its final field. The function then returns success without creating a binding and corrupts that field.

  • bin/fm-backend.sh#L500-L502: append the Zellij binding through a format-aware metadata writer.
  • bin/fm-backend.sh#L554-L556: use the same writer for the cmux binding.
  • tests/fm-teardown-endpoint-safety.test.sh#L215-L236: add a Zellij fixture with no final LF and assert a standalone binding.
  • tests/fm-teardown-endpoint-safety.test.sh#L264-L287: add the equivalent cmux regression case.
Proposed fix
+fm_backend_append_endpoint_task_id() {  # <meta-file> <task-id>
+  local meta=$1 id=$2 last_byte
+  last_byte=$(tail -c 1 "$meta") || return 1
+  [ -z "$last_byte" ] || printf '\n' >> "$meta" || return 1
+  printf 'endpoint_task_id=%s\n' "$id" >> "$meta"
+}
+
-          printf 'endpoint_task_id=%s\n' "$id" >> "$meta" || return 1
+          fm_backend_append_endpoint_task_id "$meta" "$id" || return 1
📍 Affects 2 files
  • bin/fm-backend.sh#L500-L502 (this comment)
  • bin/fm-backend.sh#L554-L556
  • tests/fm-teardown-endpoint-safety.test.sh#L215-L236
  • tests/fm-teardown-endpoint-safety.test.sh#L264-L287
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/fm-backend.sh` around lines 500 - 502, Preserve metadata record
boundaries when repairing bindings: in bin/fm-backend.sh lines 500-502, update
the Zellij repair path around fm_backend_zellij_pane_verifies_task to append
endpoint_task_id through a format-aware metadata writer that inserts a missing
final LF; apply the same writer to the cmux repair path at lines 554-556. In
tests/fm-teardown-endpoint-safety.test.sh lines 215-236 and 264-287, add
no-final-LF Zellij and cmux fixtures and assert each repaired binding is
standalone without corrupting the preceding field.

Comment thread docs/configuration.md
Comment on lines 282 to 284
```
CLAUDE_CONFIG_DIR=~/.claude-homes/account1/.claude claude /login
security add-generic-password -U -s "ccjuggler-acc<N>" -a "ccjuggler" -w "<sk-ant-oat01-… token>"
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Declare the fenced block language.

Line 282 starts an unlabeled fenced code block. Use sh to satisfy MD040.

Proposed fix
-```
+```sh
 security add-generic-password -U -s "ccjuggler-acc<N>" -a "ccjuggler" -w "<sk-ant-oat01-… token>"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
CLAUDE_CONFIG_DIR=~/.claude-homes/account1/.claude claude /login
security add-generic-password -U -s "ccjuggler-acc<N>" -a "ccjuggler" -w "<sk-ant-oat01-… token>"
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 282-282: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/configuration.md` around lines 282 - 284, Update the fenced code block
surrounding the security command to declare the sh language, changing the
opening fence from unlabeled to sh while preserving the command and closing
fence.

Source: Linters/SAST tools

Comment on lines +162 to +165
# .claude.json is pre-seeded inside CLAUDE_CONFIG_DIR (where current CC reads
# it); it must be a real per-account file, never a symlink into shared config.
[ ! -L "$home/.claude-homes/account1/.claude/.claude.json" ] || fail ".claude.json must never be a symlink"
assert_no_grep "acct1-secret" "$home/.claude-homes/account1/.claude/.claude.json" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Check the fixture value that can leak.

The fixture writes {"shared":"leak"} at Line 158, but Line 165 searches for acct1-secret. A regression that copies the shared .claude.json into the account configuration would still pass.

Proposed fix
-  assert_no_grep "acct1-secret" "$home/.claude-homes/account1/.claude/.claude.json" \
+  assert_no_grep '"shared":"leak"' "$home/.claude-homes/account1/.claude/.claude.json" \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# .claude.json is pre-seeded inside CLAUDE_CONFIG_DIR (where current CC reads
# it); it must be a real per-account file, never a symlink into shared config.
[ ! -L "$home/.claude-homes/account1/.claude/.claude.json" ] || fail ".claude.json must never be a symlink"
assert_no_grep "acct1-secret" "$home/.claude-homes/account1/.claude/.claude.json" \
# .claude.json is pre-seeded inside CLAUDE_CONFIG_DIR (where current CC reads
# it); it must be a real per-account file, never a symlink into shared config.
[ ! -L "$home/.claude-homes/account1/.claude/.claude.json" ] || fail ".claude.json must never be a symlink"
assert_no_grep '"shared":"leak"' "$home/.claude-homes/account1/.claude/.claude.json" \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/claude-account.test.sh` around lines 162 - 165, Update the fixture
assertion in the account configuration test to search for the actual shared
marker written by the fixture, “shared” or “leak,” instead of “acct1-secret.”
Keep the symlink assertion unchanged and ensure copying the shared .claude.json
into the account configuration causes the test to fail.

@trillium trillium changed the title fm-backend: extend legacy-metadata self-repair to zellij and cmux feat(fm-backend): extend legacy-metadata self-repair to zellij and cmux backends Aug 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/cmux-backend.md`:
- Around line 113-123: Remove the duplicated endpoint metadata schema and UUID
identity rules from docs/cmux-backend.md lines 113-123, preserving the legacy
self-repair paragraph and optionally linking to the existing authoritative
section. Apply the same cleanup to the duplicated Zellij schema and
pane-identity rules in docs/zellij-backend.md lines 90-102, with no changes to
the legacy self-repair paragraph.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3413950b-86ff-458d-b9d7-ded8b7ea88c1

📥 Commits

Reviewing files that changed from the base of the PR and between 6091c3e and 3cf7da5.

📒 Files selected for processing (6)
  • docs/cmux-backend.md
  • docs/configuration.md
  • docs/herdr-backend.md
  • docs/subagent-guard.md
  • docs/zellij-backend.md
  • tests/fm-teardown-endpoint-safety.test.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/fm-teardown-endpoint-safety.test.sh

Comment thread docs/cmux-backend.md
Comment on lines +113 to +123
## Endpoint metadata

```text
backend=cmux
window=<workspace-uuid>:<surface-uuid>
cmux_workspace_id=<workspace-uuid>
cmux_surface_id=<surface-uuid>
```

The UUID pair is the active endpoint authority within one app run.
Workspace UUIDs are not stable across an app relaunch, so recovery searches by the scoped title and then resolves the current surface id.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep one authoritative endpoint-metadata schema.

The new sections duplicate endpoint fields and identity rules already documented earlier in each file. Remove the repeated schema and rules, or replace them with a short link to the existing section. Keep the legacy self-repair paragraph.

  • docs/cmux-backend.md#L113-L123: remove the duplicate cmux schema and UUID rules.
  • docs/zellij-backend.md#L90-L102: remove the duplicate Zellij schema and pane-identity rules.
📍 Affects 2 files
  • docs/cmux-backend.md#L113-L123 (this comment)
  • docs/zellij-backend.md#L90-L102
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/cmux-backend.md` around lines 113 - 123, Remove the duplicated endpoint
metadata schema and UUID identity rules from docs/cmux-backend.md lines 113-123,
preserving the legacy self-repair paragraph and optionally linking to the
existing authoritative section. Apply the same cleanup to the duplicated Zellij
schema and pane-identity rules in docs/zellij-backend.md lines 90-102, with no
changes to the legacy self-repair paragraph.

Source: Coding guidelines

@trillium
trillium merged commit 47d286c into main Aug 4, 2026
20 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant